using System;
using System.Collections.Generic;
using System.Text;

namespace System
{
    /// <summary>
    /// This class encapsulates a hash operation's results - both the hash, and the algorithm used to create it
    /// </summary>
    /// <remarks>
    /// More importantly, this class includes functionality to convert the hashed data into a Guid
    /// </remarks>
	public class HashWrapper
	{
        private SharpMud.DynamicTyping.AssembliedTypeLocation _Algorithm;
        private byte[] _Data;
        
        public HashWrapper(SharpMud.DynamicTyping.AssembliedTypeLocation algorithm, byte[] data)
        {
            if (algorithm == null)
                throw new ArgumentNullException("algorithm");
            if (data == null)
                throw new ArgumentNullException("data");
            _Algorithm = algorithm;
            _Data = data;
        }

        public HashWrapper(System.Type algorithm, byte[] data)
            :this(SharpMud.DynamicTyping.AssembliedTypeLocation.FromLoadedType(algorithm),data)
        {
        }

        public static HashWrapper Hash(SharpMud.DynamicTyping.AssembliedTypeLocation hashAlgorithm, byte[] unhashedData)
        {
            return Hash(hashAlgorithm.AcquireType(), unhashedData);
        }

        public static HashWrapper Hash(System.Type algorithm, byte[] unhashedData)
        {
            if (algorithm.IsSubclassOf(typeof(System.Security.Cryptography.HashAlgorithm)))
            {
                return Hash((System.Security.Cryptography.HashAlgorithm)(algorithm.TypeInitializer.Invoke((new object[] { }))),unhashedData);
            }
            else
            {
                throw new ArgumentException("'algorithm' must derive from HashAlgorithm");
            }
        }

        public static HashWrapper Hash(System.Security.Cryptography.HashAlgorithm algorithmInstance, byte[] unhashedData)
        {
            return new HashWrapper(algorithmInstance.GetType(), algorithmInstance.ComputeHash(unhashedData));
        }
        public static HashWrapper Hash(SharpMud.DynamicTyping.AssembliedTypeLocation hashAlgorithm, string unhashedData)
        {
            System.Text.Encoding enc = System.Text.Encoding.Default;
            return Hash(hashAlgorithm, enc.GetBytes(unhashedData));
        }
        public static HashWrapper Hash(System.Type algorithm, string unhashedData)
        {
            System.Text.Encoding enc = System.Text.Encoding.Default;
            return Hash(algorithm, enc.GetBytes(unhashedData));
        }
        public static HashWrapper Hash(System.Security.Cryptography.HashAlgorithm algorithmInstance, string unhashedData)
        {
            System.Text.Encoding enc = System.Text.Encoding.Default;
            return Hash(algorithmInstance, enc.GetBytes(unhashedData));
        }
        
        public SharpMud.DynamicTyping.AssembliedTypeLocation Algorithm
        {
            get
            {
                return _Algorithm;
            }
        }
        public byte[] Data
        {
            get
            {
                return _Data;
            }
        }

        /// <summary>
        /// Takes the hash (but not the algorithm) that this object encapsulates, and transforms it into a
        /// correspondingly unique GUID
        /// </summary>
        public System.Guid ToGUID()
        {
            if (_Data == null)
                throw new InvalidOperationException();
            if (_Data.Length == 0)
                return Guid.Empty;

            Queue<byte> dbq = new Queue<byte>(_Data);
            byte[] gb = new byte[16];
            int i = 0;
            //Take bytes from the hash and fill them into the 16 byte array
            while (dbq.Count > 0)
            {
                //Loop back to index 0 if the array is full but more bytes await
                if (i == 16)
                    i = 0;
                if (gb[i] == 0)
                {
                    gb[i] = dbq.Dequeue();
                    i++; ;
                    continue;
                }
                else
                {
                    //If we are at an array index that already has a value, combine it, via exclusive-or
                    gb[i] = (byte)((int)gb[i] ^ (int)dbq.Dequeue());
                    i++;
                    continue;
                }
            }
            //If there werent enough bytes to fill the 16 byte array, repeat bytes from the beginning
            if (_Data.Length < 16)
            {
                int rem = 16 - (_Data.Length);
                for (i = ((16 - (rem)) - 1); i < 16;i++ )
                {
                    gb[i] = gb[i - (16 - (rem))];
                }
            }
            //Return the guid representable by our completed byte array
            return new Guid(gb);
        }

        public string ToURI()
        {
            System.Text.Encoding enc = System.Text.Encoding.Default;
            return "hash://" + this.Algorithm.FullTypeName + "/" + System.Uri.EscapeDataString(enc.GetString(this.Data));
        }
    }
}
